﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ELog
{
    /// <summary>
    /// 结构化的日志对象信息
    /// </summary>
    public class Log : ILog
    {

        /// <summary>
        /// 单例实体
        /// </summary>
        private static Log _singleEntry = null;

        /// <summary>
        /// 单例锁
        /// </summary>
        private static readonly string _logCreateLock = "logLock";

        /// <summary>
        /// 构建日志单例实体
        /// </summary>
        private static void CreateLogEntry()
        {
            if (_singleEntry == null)
            {
                lock (_logCreateLock)
                {
                    if (_singleEntry == null)
                    {
                        _singleEntry = new Log();
                    }
                }
            }
        }

        /// <summary>
        /// 异常输出
        /// </summary>
        /// <param name="ex">异常</param>
        /// <param name="logDir">指定的日志路径</param>
        public static void Write(Exception ex, string logDir = "")
        {
            CreateLogEntry();
            _singleEntry.Entry(ex, logDir);
        }

        /// <summary>
        /// 信息输出
        /// </summary>
        /// <param name="msg">信息</param>
        /// <param name="logDir">指定的日志路径</param>
        public static void Write(string msg, string logDir = "")
        {
            CreateLogEntry();
            _singleEntry.Entry(msg, logDir);
        }


        /// <summary>
        /// 异常输出
        /// </summary>
        /// <param name="ex">异常</param>
        /// <param name="logDir">指定的日志路径</param>
        public void Entry(Exception ex, string logDir = "")
        {
            if (ex==null) { return; }
            _logDir = logDir;
            string invokeStr = ex.TargetSite==null ? "" : ex.TargetSite.Name;
            string detailStr = ex.InnerException==null ? "" : ex.InnerException.Message;
            WriteLog("异常", ex.Message, ex.StackTrace, ex.Source, invokeStr, detailStr);
        }

        /// <summary>
        /// 信息输出
        /// </summary>
        /// <param name="msg">信息</param>
        /// <param name="logDir">指定的日志路径</param>
        public void Entry(string msg, string logDir = "")
        {
            if (msg==null||string.IsNullOrEmpty(msg.Trim())) { return; }
            _logDir = logDir;
            string source = "null";
            string invoke = "null";
            string trace = GetLogTraceInfo(0, out source, out invoke);
            trace=trace==null||string.IsNullOrEmpty(trace.Trim()) ? "未知" : trace.Trim();
            WriteLog("信息", msg, trace, source, invoke, "无");
        }
        /// <summary>
        /// 日志目录
        /// </summary>
        private string _logDir = "";

        /// <summary>
        /// 日志类型
        /// </summary>
        private string _logType = "";

        /// <summary>
        /// 日志标题
        /// </summary>
        private string _titleShow = "";

        /// <summary>
        /// 默认日志配置文件路径
        /// </summary>
        private string _logConfigPath = "";

        /// <summary>
        /// 写入日志
        /// </summary>
        /// <param name="typeStr">类型</param>
        /// <param name="msg">消息</param>
        /// <param name="traceStr">追溯</param>
        /// <param name="sourceStr">来源</param>
        /// <param name="invokeStr">调用</param>
        /// <param name="detailStr">详细</param>
        private void WriteLog(string typeStr, string msg, string traceStr, string sourceStr, string invokeStr, string detailStr)
        {
            Valid();
            if (typeStr == null||string.IsNullOrEmpty(typeStr.Trim()))
            {
                return;
            }
            if (_logType==null||string.IsNullOrEmpty(_logType.Trim()))
            {
                string logType = XmlTool.GetNodeValueByXPath("/App/Log/Type", _logConfigPath);
                if (logType==null||string.IsNullOrEmpty(logType.Trim()))
                {
                    return;
                }
                _logType=logType;
            }
            string innerTypeStr = "";
            if (typeStr=="异常")
            {
                innerTypeStr ="exception";
            }
            else if (typeStr=="信息")
            {
                innerTypeStr ="information";
            }
            else
            {
                return;
            }
            if (!_logType.ToLower().Contains(innerTypeStr))
            {
                return;
            }
            if (_titleShow==null||string.IsNullOrEmpty(_titleShow.Trim()))
            {
                string titleShow=XmlTool.GetNodeValueByXPath("/App/Log/TitleShow", _logConfigPath);
                if (titleShow==null||string.IsNullOrEmpty(titleShow.Trim()))
                {
                    titleShow="true";
                    XmlTool.SetNodeValueByXPath("/App/Log/TitleShow", titleShow, _logConfigPath);
                }
                _titleShow=titleShow;
            }
            if (_logDir==null||string.IsNullOrEmpty(_logDir.Trim()))
            {
                string logDir=XmlTool.GetNodeValueByXPath("/App/Log/Dir", _logConfigPath);
                if (logDir==null||string.IsNullOrEmpty(logDir.Trim()))
                {
                    logDir=AppDomain.CurrentDomain.BaseDirectory+"Logs";
                    if (!Directory.Exists(logDir)) 
                    {
                        Directory.CreateDirectory(logDir);
                    }
                    XmlTool.SetNodeValueByXPath("/App/Log/Dir", _logDir, _logConfigPath);
                }
                _logDir=logDir;
            }
            DateTime logTime = DateTime.Now;
            string logPath = _logDir+"\\log"+logTime.ToString("yyyy_MM_dd")+".txt";
            StringBuilder sb = new StringBuilder();
            if (_titleShow.ToLower()!="true")
            {
                sb.Append("类型=");
                sb.Append(typeStr);
                sb.Append("\t");

                sb.Append("时间=");
                sb.Append(logTime.ToString("yyyy-MM-dd HH:mm:ss"));
                sb.Append("\t");

                sb.Append("消息=");
                sb.Append(LineFmt(ExpandLine(msg)));
                sb.Append("\t");

                sb.Append("追溯=");
                sb.Append(LineFmt(ExpandLine(traceStr)));
                sb.Append("\t");

                sb.Append("来源=");
                sb.Append(LineFmt(ExpandLine(sourceStr)));
                sb.Append("\t");

                sb.Append("调用=");
                sb.Append(LineFmt(ExpandLine(invokeStr)));
                sb.Append("\t");

                sb.Append("详细=");
                sb.Append(LineFmt(ExpandLine(detailStr)));
                sb.Append("\r\n");
            }
            else
            {
                sb.Append(typeStr);
                sb.Append("\t");

                sb.Append(logTime.ToString("yyyy-MM-dd HH:mm:ss"));
                sb.Append("\t");

                sb.Append(LineFmt(ExpandLine(msg)));
                sb.Append("\t");

                sb.Append(LineFmt(ExpandLine(traceStr)));
                sb.Append("\t");

                sb.Append(LineFmt(ExpandLine(sourceStr)));
                sb.Append("\t");

                sb.Append(LineFmt(ExpandLine(invokeStr)));
                sb.Append("\t");

                sb.Append(LineFmt(ExpandLine(detailStr)));
                sb.Append("\r\n");
            }
            lock (_logLock)
            {
                if (!File.Exists(logPath))
                {
                    if (_titleShow.ToLower()=="true")
                    {
                        File.AppendAllText(logPath, "类型\t时间\t信息\t追溯\t来源\t调用\t详细\r\n");
                    }
                }
                else
                {
                    string[] lines = File.ReadAllLines(logPath);
                    if (lines.Length<=0)
                    {
                        if (_titleShow.ToLower()=="true")
                        {
                            File.AppendAllText(logPath, "类型\t时间\t信息\t追溯\t来源\t调用\t详细\r\n");
                        }
                    }
                }
                File.AppendAllText(logPath, sb.ToString());
            }
        }

        /// <summary>
        /// 日志配置文件的校验，如果不存在将写入默认配置
        /// </summary>
        private void Valid()
        {
            if (_logConfigPath==null||string.IsNullOrEmpty(_logConfigPath.Trim()))
            {
                _logConfigPath=AppDomain.CurrentDomain.BaseDirectory+"Config\\LogConfig.xml";
            }
            string configStr = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<App>\r\n\t<Log>\r\n\t\t<Dir></Dir>\r\n\t\t<Type>exception,information</Type>\r\n\t\t<TitleShow>True</TitleShow>\r\n\t</Log>\r\n</App>";
            CreateDefaultXml(_logConfigPath, configStr);
        }

        /// <summary>
        /// 构建默认的xml文件
        /// </summary>
        /// <param name="xmlFilePath">xml文件路径</param>
        /// <param name="defaultTxt">默认文本</param>
        private void CreateDefaultXml(string xmlFilePath, string defaultTxt) 
        {
            if (xmlFilePath==null||string.IsNullOrEmpty(xmlFilePath.Trim())||defaultTxt==null||string.IsNullOrEmpty(defaultTxt.Trim())) 
            {
                return;
            }
            xmlFilePath=xmlFilePath.Replace("\\", Path.DirectorySeparatorChar.ToString());
            if (!File.Exists(xmlFilePath))
            {
                string logPath = Path.GetDirectoryName(xmlFilePath);
                if (logPath==null||string.IsNullOrEmpty(logPath.Trim()))
                {
                    return;
                }
                if (!Directory.Exists(logPath))
                {
                    Directory.CreateDirectory(logPath);
                }
                File.WriteAllText(xmlFilePath, defaultTxt);
            }
        }

        /// <summary>
        /// 日志锁
        /// </summary>
        private static readonly string _logLock = "logLock";
        /// <summary>
        /// 字符串格式化
        /// </summary>
        /// <param name="line">字符串</param>
        /// <returns>格式化后的字符串</returns>
        private string LineFmt(string line)
        {
            return (line==null||string.IsNullOrEmpty(line.Trim())) ? "未知" : line.Trim();
        }

        /// <summary>
        /// 多行字符串展开为单行
        /// </summary>
        /// <param name="lines">多行字符串</param>
        /// <returns>单行字符串</returns>
        private string ExpandLine(string lines)
        {
            if (lines==null||string.IsNullOrEmpty(lines.Trim()))
            {
                return null;
            }
            return string.Join(";", lines.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList().Select(a => a.Trim()).ToArray());
        }

        /// <summary>
        /// 获得日志追溯信息
        /// </summary>
        /// <param name="skipTrace">跳过的级别</param>
        /// <param name="source">日志来源信息，外传参数</param>
        /// <param name="invoke">日志调用信息，外传参数</param>
        /// <returns>追溯信息</returns>
        private string GetLogTraceInfo(int skipTrace, out string source, out string invoke)
        {
            StackTrace trace = new StackTrace(skipTrace, true);
            source = GetSourceAndInvoke(trace, out invoke);
            return LineFmt(FormatTrace(trace.ToString()));
        }

        /// <summary>
        /// 格式化追溯行
        /// </summary>
        /// <param name="traceLines">追溯行</param>
        /// <returns>格式化后的追溯行</returns>
        private string FormatTrace(string traceLines)
        {
            if (traceLines==null||string.IsNullOrEmpty(traceLines.Trim()))
            {
                return null;
            }
            string selfStr = "";
            //取得当前代码的类名    
            selfStr=  MethodBase.GetCurrentMethod()?.DeclaringType?.FullName;
            if (selfStr==null||string.IsNullOrEmpty(selfStr.Trim()))
            {
                selfStr="未知";
            }
            List<string> traces = traceLines.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            List<string> filterTraces = traces.FindAll(a => (!a.Contains(selfStr))&&(a.Contains("cs:line")||a.Contains("行号")));
            if (filterTraces==null|| filterTraces.Count<=0)
            {
                return null;
            }
            else
            {
                return string.Join(";", filterTraces.ToArray());
            }
        }

        /// <summary>
        /// 根据追踪信息获取日志来源和调用
        /// </summary>
        /// <param name="trace">追溯信息</param>
        /// <param name="invoke">日志调用，外传参数</param>
        /// <returns>日志来源</returns>
        private string GetSourceAndInvoke(StackTrace trace, out string invoke)
        {
            string selfFileName = System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.Name;
            string methodName = "未知";
            string fileName = "未知";
            for (int i = 0; i<trace.FrameCount; i++)
            {
                StackFrame myFrame = trace.GetFrame(i);
                if (myFrame == null)
                {
                    continue;
                }
                string fName = myFrame.GetFileName();
                if (fName==null||string.IsNullOrEmpty(fName.Trim()))
                {
                    continue;
                }
                string fTmp = Path.GetFileNameWithoutExtension(fName);
                if (fTmp==selfFileName)
                {
                    continue;
                }
                MethodBase mb = myFrame.GetMethod();
                methodName=mb==null ? "未知" : mb.Name;
                fileName=myFrame.GetFileName();
                break;
            }
            invoke = (methodName==null||string.IsNullOrEmpty(methodName.Trim())) ? "未知" : methodName.Trim();
            return (fileName==null||string.IsNullOrEmpty(fileName.Trim())) ? "未知" : fileName.Trim();
        }
    }
}
